/* ***** BEGIN LICENSE BLOCK *****
 *
 * TakeControl Firefox extension
 * Copyright (C) 2006  Dafydd Jones <takecontrol.extension@googlemail.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 *
 * ***** END LICENSE BLOCK ***** */

#include "TkctrlConstants.jsx"

const DEBUG = true;		/* set to true to enable debug messages */
const VERBOSE = false;	/* set to true to enable verbose debug messages */

const TKCTRL_STORE = "tkctrl_store.rdf";

const CATMAN_CID = "@mozilla.org/categorymanager;1";
const TIMER_CID = "@mozilla.org/timer;1";
const OBSERVICE_CID = "@mozilla.org/observer-service;1";
const WINWATCH_CID = "@mozilla.org/embedcomp/window-watcher;1";

/** returns the date in the format YYYYMMDD */
Date.prototype.getW3CDate = function () {	
	var month = this.getMonth() + 1;
	month = ((month >= 0 && month <= 9) ? "0" : "") + month;
	var day = this.getDate().toString();
	day = ((day >= 0 && day <= 9) ? "0" : "") + day;
	return this.getFullYear().toString() + "-" + month + "-" + day;
}

/****************************
 * XPCOM components
 ****************************/
function TakeControlService() {
	this._timers = 	{};
	this._store = 	null;
	this._storeFilePath = null;
	this._paused = 	false;
	this._activeLocation = TakeControlService.nullDOMLocation;
	this._database = null;
}
TakeControlService.prototype = {
	CID: Components.ID("{49fff695-1be2-4600-958c-8b4b848c7e38}"),
	className: TKCTRL_SERVICE_NAME,
	contractID: TKCTRL_SERVICE_CID,

	get storeFilePath() { return this._storeFilePath },

	get siteDuration() {
		return this._store.siteDuration;
	},

	get totalDuration() {
		return this._store.totalDuration 
	},

	get paused() { return this._paused },
	set paused(x) {
		debug("paused = " + x);
		this._paused = x;
		this._notify(TKCTRL_DISPLAY);
	},

	get active() { return this._store.active },
	set active(x) {		
		this._store.active = x;
		this._notify(TKCTRL_DISPLAY);
	},
}
TakeControlService.prototype.observe = function (subject, topic, data) {
	switch (topic) {
		case "xpcom-startup":
			debug("xpcom-startup");

			var obsSvc = CC[OBSERVICE_CID].getService(CI.nsIObserverService);
			obsSvc.addObserver(this, "profile-after-change", true);
			obsSvc.addObserver(this, "quit-application", true);
			
			break;

		case "profile-after-change":
			debug("profile-after-change");

			this._setDates();
			this._storeFilePath = this._initFilePath(TKCTRL_STORE);
			this._database = new Database(this.storeFilePath);
			this._store = new Store(this._today, this._database);

			//TODO work out more elegant way of initialising these timers
			this._timers.quarter = CC[TIMER_CID].createInstance(CI.nsITimer);
			this._timers.quarter.init(this, 250, this._timers.quarter.TYPE_REPEATING_SLACK);
			this._timers.second = CC[TIMER_CID].createInstance(CI.nsITimer);
			this._timers.second.init(this, 1000, this._timers.second.TYPE_REPEATING_SLACK);
			this._timers.minute = CC[TIMER_CID].createInstance(CI.nsITimer);
			this._timers.minute.init(this, 60000, this._timers.minute.TYPE_REPEATING_SLACK);
			break;

		case "timer-callback":
			switch (subject) {
				case this._timers.quarter:
					verbose("quarter");
					//every quarter second, check if the active location has changed
					if (this._locationChanged()) {
						this._store.changeSite(this._activeLocation);
						this._notify(TKCTRL_DISPLAY); 
					}						
					break;

				case this._timers.second:
					verbose("second");						

					//only increment time if not paused
					if (!this.paused) {							
						this._store.increment();					
					}

					this._notify(TKCTRL_DISPLAY);
					break;

				case this._timers.minute:
					debug("minute");
					//check for new day
					var nowLocal = new Date();
					if (nowLocal.getTime() >= this._tomorrowDate.getTime()) {
						this._triggerDateChange();
					}

					//each minute we save the stats
					this._store.flush();
			}
			break;

		case "quit-application":
			debug("quit-application");
			for each (var timer in this._timers) { timer.cancel() };

			this._store.flush();

			var obsSvc = CC[OBSERVICE_CID].getService(CI.nsIObserverService);
			obsSvc.removeObserver(this, "profile-after-change");
			obsSvc.removeObserver(this, "quit-application");
	}
}

TakeControlService.prototype.decrement = function TCS_decrement(interval) {
	this._store.decrement(interval);
	this._notify(TKCTRL_DISPLAY);
}

TakeControlService.prototype.increment = function TCS_increment(interval) {
	this._store.increment(interval);
	this._notify(TKCTRL_DISPLAY);
}

TakeControlService.prototype.QueryInterface = function (iid) {
	verbose("QueryInterface(" + iid + ")");
	if (!iid.equals(CI.ITakeControlService)
		&& !iid.equals(CI.nsISupports)
		&& !iid.equals(CI.nsIObserver)
		&& !iid.equals(CI.nsISupportsWeakReference) )
		throw CR.NS_ERROR_NO_INTERFACE;
	return this;
}

TakeControlService.prototype._notify = function TCS__notify(topic) {
	CC[OBSERVICE_CID].getService(CI.nsIObserverService)
						.notifyObservers(null, topic, null);
}

TakeControlService.prototype._locationChanged = function TCS__locationChanged() {
	if (!this.winWatch) {
		this.winWatch = CC[WINWATCH_CID].getService(CI.nsIWindowWatcher);
	}

	var newLocation;
	var locationChanged = false;
	var win = this.winWatch.activeWindow;
	
	if (win && win.content)  {
		//create a JS location object, due to vagaries of XPConnect
		newLocation = new Location(win.content.location);
	}
	else {
		// active window is not a browser
		// create null location object
		newLocation = TakeControlService.nullDOMLocation;
	}
	verbose("comparing " + this._activeLocation.href + " to " + newLocation.href);
	if (newLocation.href !== this._activeLocation.href) {
		debug("location changed from: "  + this._activeLocation +
				" to: " + newLocation);
		this._activeLocation = newLocation;
		locationChanged = true;
	}
	
	return locationChanged;
}

TakeControlService.prototype._setDates = function TCS__setDates() {
	var now = new Date();
	this._today = now.getW3CDate();
	this._tomorrowDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
	debug("today = " + this._today + "; tomorrow = " + this._tomorrowDate.getW3CDate());
}

TakeControlService.prototype._triggerDateChange = function TCS__triggerDateChange() {
	debug("switched to new day");
	this._setDates();
	this._store.triggerDateChange(this._today);
	this._notify(TKCTRL_DISPLAY);
}

TakeControlService.prototype._initFilePath = function TCS__initFilePath(storeFileName) {
	var dirService = CC["@mozilla.org/file/directory_service;1"].getService(CI.nsIProperties);
	var path = dirService.get("ProfD", Components.interfaces.nsIFile);
	path.append(storeFileName);
	verbose(path + " " + path.path);

	/* there are warnings about using the string value of an nsIFile, so
	   we have to(?) convert to an nsIURI */
	var IOService = CC["@mozilla.org/network/io-service;1"].getService(CI.nsIIOService);
	var storeFileURI = IOService.newFileURI(path);
	debug("constructed store file path: " + storeFileURI.spec);
	return storeFileURI.spec;
}

function Location(nsIDOMLocation) {
	try {
		this.href = nsIDOMLocation.href;
		this.protocol = nsIDOMLocation.protocol;
		this.port = nsIDOMLocation.port;
		this.host = nsIDOMLocation.host;
		this.hostname = nsIDOMLocation.hostname;
		this.pathname = nsIDOMLocation.pathname;
		this.search = nsIDOMLocation.search;
	}
	catch (e) {
		//any exception here means we create dummy values
		this.protocol = "about:";
		this.href = "about:takecontrol";
	}
}
Location.prototype.toString = function () { return this.href; }

TakeControlService.nullDOMLocation = {
	protocol: "about:",
	href: "about:takecontrol",
	toString: function () { return this.href }
}

/***************
 * module object
 ***************/
var takeControlModule = {};

takeControlModule.registerSelf = function (compMgr, fileSpec, loaderStr, type) {
    debug("registering component");
    compMgr = compMgr.QueryInterface(CI.nsIComponentRegistrar);

    for each (var obj in this._objects) {
		for (var prop in obj) verbose(prop + ":" + obj[prop]);
		compMgr.registerFactoryLocation(obj.CID, obj.className,
										obj.contractID, fileSpec, loaderStr, type);
    }

	var catMan = CC[CATMAN_CID].getService(CI.nsICategoryManager);
	catMan.addCategoryEntry("xpcom-startup",
                            TKCTRL_SERVICE_NAME,
                            TKCTRL_SERVICE_CID,
                            true,
                            true);
	debug("done registering component");
}

takeControlModule.unregisterSelf = function(compMgr, location, loaderStr) {
	compMgr.QueryInterface(CI.nsIComponentRegistrar);
    for each (var obj in this._objects) {      
      compMgr.unregisterFactoryLocation(obj.CID, location);
    }

	var catMan = CC[CATMAN_CID].getService(CI.nsICategoryManager);
	catMan.deleteCategoryEntry("xpcom-startup", TKCTRL_SERVICE_NAME, true);
}

takeControlModule.getClassObject = function (compMgr, cid, iid) {
    if (!iid.equals(CI.nsIFactory))
        throw CR.NS_ERROR_NOT_IMPLEMENTED;

	for (var i in this._objects) {
		if (cid.equals(this._objects[i].CID))
        	return this._objects[i].factory;
    }

    return CR.NS_ERROR_NO_INTERFACE;
}

takeControlModule.canUnload = function (compMgr) {
    debug("unloading component");
    return true;
}

takeControlModule._objects = {}

/* array of objects we want to XPCOMify */
var objects = [TakeControlService];

/* module entrypoint */
function NSGetModule(compMgr, fileSpec) {
	debug("NSGetModule");
	for (var i in objects) {
		//create a new set of XPCOM objects with factories
		takeControlModule._objects[i] = new FactoryHolder(objects[i]);
	}
    return takeControlModule;
}

function FactoryHolder(obj) {
	this.CID        = obj.prototype.CID;
	this.contractID = obj.prototype.contractID;
	this.className  = obj.prototype.className;
	this.factory = {
		createInstance: function(outer, iid) {
			verbose("createInstance(" + iid + ")");
			if (outer != null) throw CR.NS_ERROR_NO_AGGREGATION;
			return (new this.constructor).QueryInterface(iid);
		}
	};
	this.factory.constructor = obj;
	for (var prop in obj) verbose(prop + " : " + obj[prop]);
}

/* logging functions */
var debug = DEBUG ? function (s) { dump("tkctrl xpcom: " + s + "\n"); } : function (s) {}
var	verbose = VERBOSE ? function (s) { dump("tkctrl xpcom: " + s + "\n"); } : function (s) {}

#include "Store.jsx"
#include "Database.jsx"
#include "DatabaseEntry.jsx"
#include "SiteEntry.jsx"
